package com.example.task;

import HslCommunication.BasicFramework.SoftBasic;
import HslCommunication.Core.Transfer.IByteTransform;
import HslCommunication.Profinet.Siemens.SiemensS7Net;
import com.alibaba.excel.util.StringUtils;
import com.example.config.ClientGen;
import com.example.constant.OpcConstant;
import com.example.entity.DbBlock;
import com.example.entity.DbBlockDto;
import com.example.enums.TypeEnum;
import com.example.event.OpcMessageEvent;
import com.example.exception.OpcException;
import com.example.s7.PLCUtils;
import com.example.s7.PlcSectionTwoConnectorFactory;
import com.example.service.DbBlockService;
import com.example.strategy.PLCDataConvertStrategyFactory;
import com.example.utils.HiveUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * Copyright: Copyright (c) 2022
 *
 * <p>
 * Description:
 *
 * @author LiuFeng
 * @version 2022/11/16 11:20
 * @since
 */
@RestController
@Component
@EnableScheduling
@Slf4j
public class OpcTask_BAK {

    @Resource
    private DbBlockService dbBlockService;

    @Resource
    private PLCDataConvertStrategyFactory factory;

    @Resource
    private ApplicationEventPublisher eventPublisher;

    /**
     * 存储一标段DB块的数据
     */
    List<NodeId> sectionOneBlocks1 = null;
    List<DbBlock> sectionOneBlocks = null;

    /**
     * 存储二标段DB块的数据
     */
    List<NodeId> sectionTwoBlocks1 = null;

    List<DbBlock> sectionTwoBlocks = null;

    private ObjectMapper mapper;

    private ThreadPoolExecutor executor;

    //PLC数据转换
    private IByteTransform byteTransform;

    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    /**
     * 初始化线程池和json序列化对象，以及读取DB块的数据
     */
    //@PostConstruct
    public void init() {
        int processors = Runtime.getRuntime().availableProcessors();
        int poolSize = processors * 2;

        LinkedBlockingQueue queue = new LinkedBlockingQueue(10000);
        //自定义线程池，
        /*executor = new ThreadPoolExecutor(poolSize,
                poolSize * 2, 60, TimeUnit.SECONDS,
                queue, new OpcThreadFactory("opc101"));*/

        mapper = new ObjectMapper();
        if (sectionTwoBlocks == null) {
            // 读取一标段DB块的数据，并拼接成t|xxx的格式
            sectionOneBlocks1 = dbBlockService.readSectionOneInfo();
            // 读取二标段的数据，并拼接成t|xxx的格式
            sectionTwoBlocks1 = dbBlockService.readSectionTwoInfo1();
            sectionTwoBlocks1.addAll(sectionOneBlocks1);
            // sectionTwoBlocks = dbBlockService.readSectionTwoInfo();
        }
        try {
            SiemensS7Net siemensS7Net = PlcSectionTwoConnectorFactory.borrowObject();
            byteTransform = siemensS7Net.getByteTransform();
            PlcSectionTwoConnectorFactory.returnObject(siemensS7Net);
        } catch (Exception ex) {
            throw new OpcException(ex);
        }
    }

    /**
     * 批量采集PLC数据 @@@
     */
    //@RequestMapping("/")
    //@Scheduled(fixedRate = 1, timeUnit = TimeUnit.SECONDS)
    public void batchReadPLCData() {
        long startTime = System.nanoTime();
        try {
            // 采集二标段的数据
            //List<DbBlockDto> dtoTwo = gatherSectionTwoData();
            // 采集PLC数据（一和二标段）
            List<DbBlockDto> dbBlockDtoList = gatherSectionOneData(sectionTwoBlocks1);
            // 将数据发送给Spring事件，以异步的形式将数据发送到kafka
            eventPublisher.publishEvent(new OpcMessageEvent(this, dbBlockDtoList));
            log.info("读取数据耗时：{} 毫秒, 数据量：{}", Duration.ofNanos(System.nanoTime() - startTime).toMillis(), dbBlockDtoList.size());
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 每隔10min存储plc数据到hive
     */
    //@RequestMapping("/")
    //@Scheduled(fixedRate = 10, timeUnit = TimeUnit.MINUTES)
    public void savePLCData2Hive() {
        long startTime = System.nanoTime();
        try {
            log.info("待读取的opc变量个数为：{}", sectionTwoBlocks1.size());
            // 采集PLC数据（一和二标段）
            List<DbBlockDto> dbBlockDtoList = gatherSectionOneData(sectionTwoBlocks1);
            // 存hive库
            save2Hive(dbBlockDtoList);
            log.info("完成hive存储，耗时：{} 毫秒, 数据量：{}", Duration.ofNanos(System.nanoTime() - startTime).toMillis(), dbBlockDtoList.size());
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException | SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 存hive库
     */
    public void save2Hive(List<DbBlockDto> dbBlockDtoList) throws SQLException {
        Connection conn = HiveUtils.getConn();
        log.info("hive数据库的conn 是：{}", conn);
        PreparedStatement stmts = conn.prepareStatement("insert into gather_data" + " values(?,?,?)");
        stmts.setString(1, UUID.randomUUID().toString());
        stmts.setString(2, getMessage(dbBlockDtoList));
        stmts.setString(3, SIMPLE_DATE_FORMAT.format(new Date()));
        stmts.execute();
    }

    /**********************************************/
    /*                以下为废弃代码                */
    /**********************************************/

    //    @Scheduled(fixedRate = 1, timeUnit = TimeUnit.SECONDS)
    @Deprecated
    public void readDbInfo() {
        long startTime = System.nanoTime();
        List<DbBlock> dbBlocks = sectionTwoBlocks.parallelStream()
//                .filter(dbBlock ->
//                        dbBlock.getName().equals("W301_annual_output")
//                                || dbBlock.getName().equals("W301_daily_output")
//                                || dbBlock.getName().equals("W301_momentary")
//                                || dbBlock.getName().equals("W301_monthly_output")
//                                || dbBlock.getName().equals("W301_shift_output")
//
//                )
                .sorted(Comparator.comparing(DbBlock::getAddrParams))
                .collect(Collectors.toList());
        // 把二进制类型的数据独立提出（二进制数据需要单独处理）
        List<DbBlock> binaryBlocks = dbBlocks.parallelStream()
                .filter(block -> block.getType().equals(TypeEnum.BINARY.getType()))
                .collect(Collectors.toList());

        //将二进制变量类型的数据和其他类型的数据分割开（dbBlocks中只留下非二进制的）
        dbBlocks.removeAll(binaryBlocks);

        log.debug("将要查询的二进制变量数据是：{}", binaryBlocks.size());// 6000个（第一波的7000个字段）
        log.debug("将要查询的其他类型数据是：{}", dbBlocks.size());// 1500个（第一波的7000个字段）

        //创建一个存储CompletableFuture的数组，存储异步调用的对象
        List<CompletableFuture<?>> futures = new ArrayList<>(1000);


        //进行异步调用，并将其添加到异步调用的数组
        // future：一个任务  futures：任务池 executor：线程
        // 下面的for循环：把1W个字段的数据读取任务，交给线程池去执行：一个线程，对应一个connect，对应一个任务
        // 处理非二进制字段
        for (DbBlock block : dbBlocks) {
            //相同地址的数据只读取一次
            CompletableFuture<DbBlockDto> future = CompletableFuture.supplyAsync(() -> {
                // PLC数据读取任务
                DbBlockDto dbBlockDto = getDbBlockDto(block);
                return dbBlockDto;
            }, executor);

            futures.add(future);
        }

        // 处理二进制字段
        List<CompletableFuture<?>> binaryFutures = wrapBinaryBlockFuture(binaryBlocks);


        List<DbBlockDto> dto = new ArrayList<>(1000);

        //获取异步调用的结果，并将其添加到dto数组
        for (CompletableFuture<?> future : futures) {
            try {
                DbBlockDto dbBlockDto = (DbBlockDto) future.get();
                dto.add(dbBlockDto);
            } catch (Exception e) {
                throw new OpcException(e);
            }
        }

        log.info("其他类型的数据量是：{}", dto.size());

        List<DbBlockDto> binaryDto = new ArrayList<>(2000);
        for (CompletableFuture<?> future : binaryFutures) {
            try {
                DbBlockDto dbBlockDto = (DbBlockDto) future.get();
                binaryDto.add(dbBlockDto);
            } catch (Exception e) {
                throw new OpcException(e);
            }
        }

        log.info("，耗时：{} seconds", Duration.ofNanos(System.nanoTime() - startTime).getSeconds());

        log.info("查询出来的二进制变量的数据块数量是：{}", binaryDto.size());

        // 对二进制字段的值进行拆分，并赋值
        List<DbBlockDto> result = getBinaryDbBlockDtos(binaryBlocks, binaryDto);

        log.info("重新调整后的二进制变量的数据块数量是：{}", result.size());

        // result：二进制字段读出的值，dto：非二进制字段读出的值
        result.addAll(dto);

        //将dto序列化为JSON格式的字符串
        String message = getMessage(result);

        log.info("读取数据耗时：{} seconds", Duration.ofNanos(System.nanoTime() - startTime).getSeconds());

        //将信息发送到kafka的broker 发送到spring事件队列中
//        kafkaService.sendMessage(message);
        Duration duration = Duration.ofNanos(System.nanoTime() - startTime);
//        log.info("获取信息：{}", message);
        log.info("数量是：{}，，耗时：{} seconds", result.size(), duration.getSeconds());

    }

    /**
     * 对查询出来的二进制变量数据进行重新包装，因为查询的值是byte，而实际值是按bit存储的，需要将byte值转为bit
     *
     * @param binaryBlocks
     * @param dto
     * @return
     */
    private List<DbBlockDto> getBinaryDbBlockDtos(List<DbBlock> binaryBlocks, List<DbBlockDto> dto) {
        //将读取的数据进行转化，只处理 二进制变量 的数据
//        Map<String, DbBlock> map = binaryBlocks.stream()
//                .collect(Collectors.toMap(b -> b.getCGroup() + b.getAddrParams(), b -> b));
        Map<String, DbBlock> map = blockstoMap(binaryBlocks);
        //对数据进行整合
        List<DbBlockDto> result = new ArrayList<>();

        for (DbBlockDto blockDto : dto) {

            //blockDto.getValue() 查询出来的是byte值，对应8个数据块的值，一个bit对应一个数据块的值，因此需要进行分割
            for (int index = 0; index < 8; index++) {

                String offset = blockDto.getAddrParams().split(OpcConstant.PERIOD_REG)[0];
                DbBlock block = map.get(blockDto.getGroup() + offset + OpcConstant.PERIOD + index);

                //如果block不存在，或者block数据的类型不是二进制变量，则跳出循环
                if (block == null || !block.getType().equals(TypeEnum.BINARY.getType())) {
                    continue;
                }

                if (blockDto.getValue() != null) {
                    DbBlockDto dbBlockDto = transformBinaryBlockDto(block, blockDto);
                    result.add(dbBlockDto);
                }
            }
        }
        return result;
    }

    private Map<String, DbBlock> blockstoMap(List<DbBlock> blocks) {
        Map<String, DbBlock> map = new HashMap(7000);
        for (DbBlock block : blocks) {
            map.put(block.getCGroup() + block.getAddrParams(), block);
        }
        return map;
    }

    /**
     * 创建一个二进制变量数据点位查询的CompletableFuture数组，进行异步查询
     *
     * @param binaryBlocks 二进制变量类型的数据块
     * @return 数组
     */
    private List<CompletableFuture<?>> wrapBinaryBlockFuture(List<DbBlock> binaryBlocks) {
        List<CompletableFuture<?>> binaryFutures = new ArrayList<>(2000);
        String address = null;
        for (DbBlock block : binaryBlocks) {
            //相同地址的二进制变量数据只读取一次
            if (block.getAddrParams().contains(OpcConstant.PERIOD)) {
                String temp = block.getAddrParams().split(OpcConstant.PERIOD_REG)[0];
                if (StringUtils.isNotBlank(address) && temp.equals(address)) {
                    continue;
                }
                address = temp;
            }

            CompletableFuture<DbBlockDto> future = CompletableFuture.supplyAsync(() -> {
                DbBlockDto dbBlockDto = getDbBlockDto(block);
                return dbBlockDto;
            }, executor);

            binaryFutures.add(future);
        }
        return binaryFutures;
    }

    /**
     * 将查询出来的二进制变量数据库的值进行重新整合，
     *
     * @param block 二进制变量数据块
     * @param dto   查询出来的二进制变量数据块
     * @return
     */
    private DbBlockDto transformBinaryBlockDto(DbBlock block, DbBlockDto dto) {
        DbBlockDto blockDto = new DbBlockDto();
        blockDto.setName(block.getName());
        blockDto.setGroup(block.getCGroup());
        blockDto.setAddrParams(block.getAddrParams());
        String value = dto.getValue();
        String str = block.getAddrParams().split(OpcConstant.PERIOD_REG)[1];
        int index = Integer.parseInt(str);
        value = new StringBuilder(value).reverse().toString();
        if (value.length() > index) {

            char c = value.charAt(index);
            blockDto.setValue(String.valueOf(c));
        }
        return blockDto;
    }

    private String getMessage(List<DbBlockDto> dto) {
        try {
            return mapper.writeValueAsString(dto);
        } catch (JsonProcessingException ex) {
            throw new OpcException(ex);
        }
    }

    /**
     * 查询PLC数据，并将其转化dto
     *
     * @param block 数据块
     * @return dto
     */
    private DbBlockDto getDbBlockDto(DbBlock block) {
        //查询PLC数据
        String value = factory.convertData(block);

        DbBlockDto dbBlockDto = new DbBlockDto();
        dbBlockDto.setAddrParams(block.getAddrParams());
        dbBlockDto.setName(block.getName());
        dbBlockDto.setGroup(block.getCGroup());
        dbBlockDto.setValue(value);
        return dbBlockDto;
    }

    /**
     * 采集一标段和二标段的数据
     */
    private List<DbBlockDto> gatherSectionOneData(List<NodeId> nodeIds) throws ExecutionException, InterruptedException {
        List<DbBlockDto> dtoList = new ArrayList<>();
        OpcUaClient client = ClientGen.opcUaClient;
        log.info("client is: {}", client);
        client.connect().get();
        // 批量采集数据
        final CompletableFuture<List<DataValue>> data = client.readValues(0d, TimestampsToReturn.Neither, nodeIds);
        List<DataValue> dataValues = data.get();
        if (dataValues == null) {
            return dtoList;
        }
        // 封装数据
        for (int i = 0; i < dataValues.size(); i++) {
            DbBlockDto dbBlockDto = new DbBlockDto();
            DataValue value = dataValues.get(i);
            dbBlockDto.setName(nodeIds.get(i).getIdentifier().toString().substring(2));
            if(value.getStatusCode() != null){
                String statusCode = value.getStatusCode().toString();
                if (value.getStatusCode().isGood()) {
                    String plcValue = value.getValue().getValue().toString();
                    dbBlockDto.setValue(plcValue);
                    //dbBlockDto.setGroup("nice");
                } else {
                    //dbBlockDto.setGroup("exception-name-" + dbBlockDto.getName() + "-cause-" + statusCode);
                    //log.info("======== >  读数据异常，未获取到数据，StatusCode = {}", statusCode);
                }
            } else {
                //dbBlockDto.setGroup("exception-name-" + dbBlockDto.getName());
                //log.error("===== >  读数据异常，未获取到数据 < =====");
            }
            dtoList.add(dbBlockDto);
        }
        //List<DbBlockDto> dtoList1 = dtoList.stream().filter(p -> p.getGroup().contains("exception")).collect(Collectors.toList());
        //log.info("dtoList: {}", getMessage(dtoList));
        return dtoList;
    }

    /**
     * 采集二标段的数据
     *
     * @return
     */
    private List<DbBlockDto> gatherSectionTwoData() {
        Map<String, List<DbBlock>> map = sectionTwoBlocks.stream()
                .sorted(Comparator.comparing(DbBlock::getAddrParams))
                .collect(Collectors.groupingBy(DbBlock::getType));

        // 划分4中类型的字段（做不同的处理）
        // 无符号的32位没处理？
        List<DbBlock> binaryBlocks = map.get(TypeEnum.BINARY.getType());
        List<DbBlock> noSign8Blocks = map.get(TypeEnum.NO_SIGN8_BIT.getType());
        List<DbBlock> noSign16Blocks = map.get(TypeEnum.NO_SIGN16_BIT.getType());
        List<DbBlock> floatBlocks = map.get(TypeEnum.FLOAT_32_BIT.getType());

        // 过滤二进制类型的冗余字段（只留一个有效字段）
        List<DbBlock> filterBinaryBlocks = filterBinaryBlocks(binaryBlocks);

        // 构建PLC任务集合
        List<CompletableFuture> binaryFutures = readPlcFuture(filterBinaryBlocks);
        List<CompletableFuture> noSign8Futures = readPlcFuture(noSign8Blocks);
        List<CompletableFuture> noSign16Futures = readPlcFuture(noSign16Blocks);
        List<CompletableFuture> floatFutures = readPlcFuture(floatBlocks);

        // 获取异步读取到的PLC实时数据
        List<Map<String, Object>> binaryByte = getPlcDataFromFuture(binaryFutures);
        List<Map<String, Object>> noSign8Byte = getPlcDataFromFuture(noSign8Futures);
        List<Map<String, Object>> noSign16Byte = getPlcDataFromFuture(noSign16Futures);
        List<Map<String, Object>> floatByte = getPlcDataFromFuture(floatFutures);

        // 完成数据格式转换（从二进制数组->相应类型）
        List<DbBlockDto> noSign8BlockDto = transformByte2NoSign8(noSign8Byte);
        List<DbBlockDto> noSign16BlockDto = transformByte2NoSign16(noSign16Byte);
        List<DbBlockDto> floatBlockDto = transformByte2Float(floatByte);
        List<DbBlockDto> binaryBlockDto = transformByte2Binary(binaryByte, binaryBlocks);
//        String message = getMessage(binaryBlockDto);
//        log.info("数据是：{}", message);

        binaryBlockDto.addAll(noSign8BlockDto);
        binaryBlockDto.addAll(noSign16BlockDto);
        binaryBlockDto.addAll(floatBlockDto);
        return binaryBlockDto;
    }

    private List<DbBlockDto> transformByte2Binary(List<Map<String, Object>> list, List<DbBlock> binaryBlocks) {
        List<DbBlockDto> dto = new ArrayList<>();
        StringBuilder builder = new StringBuilder();
        for (Map<String, Object> map : list) {
            List<DbBlock> blocks = (List<DbBlock>) map.get("blocks");
            if (map.get("value") == null) continue;
            byte[] bytes = (byte[]) map.get("value");
            int count = 0;
            for (DbBlock block : blocks) {
                Byte byteValue = bytes[count++];
                String binaryValue = PLCUtils.getBinaryStrFromByte(byteValue);

                String address = block.getAddrParams().split(OpcConstant.PERIOD_REG)[0];
                List<DbBlock> tempList = binaryBlocks.stream().filter(p -> p.getAddrParams().startsWith(address))
                        .collect(Collectors.toList());
                for (DbBlock dbBlock : tempList) {

                    if (!dbBlock.getAddrParams().contains(OpcConstant.PERIOD))
                        continue;
                    String[] split = dbBlock.getAddrParams().split(OpcConstant.PERIOD_REG);
                    if (!split[0].equals(address))
                        continue;
                    DbBlockDto dbBlockDto = new DbBlockDto();
                    dbBlockDto.setName(dbBlock.getName());
                    dbBlockDto.setAddrParams(dbBlock.getAddrParams());
                    StringBuilder reverseValue = builder.append(binaryValue).reverse();
                    char c = reverseValue.charAt(Integer.parseInt(split[1]));
                    dbBlockDto.setValue(String.valueOf(c));
                    dto.add(dbBlockDto);
                }

            }

        }
        log.debug("二进制变量数据数量为：{}", dto.size());

        return dto;
    }

    private List<DbBlockDto> transformByte2Float(List<Map<String, Object>> list) {
        List<DbBlockDto> dto = new ArrayList<>();

        for (Map<String, Object> map : list) {
            List<DbBlock> blocks = (List<DbBlock>) map.get("blocks");
            if (map.get("value") == null) continue;
            byte[] bytes = (byte[]) map.get("value");
            int count = 0;
            for (DbBlock block : blocks) {
                DbBlockDto blockDto = new DbBlockDto();
                blockDto.setName(block.getName());
                blockDto.setAddrParams(block.getAddrParams());
                byte[] valueBytes = Arrays.copyOfRange(bytes, count, count + block.getLength());
                float value = byteTransform.TransSingle(valueBytes, 0);
                blockDto.setValue(String.valueOf(value));
                dto.add(blockDto);
                count += block.getLength();
                log.debug("Float数据，地址是：{}， 值是：{}", block.getAddrParams(), value);
            }


        }

        return dto;
    }

    private List<DbBlockDto> transformByte2NoSign16(List<Map<String, Object>> list) {
        List<DbBlockDto> dto = new ArrayList<>();

        for (Map<String, Object> map : list) {
            List<DbBlock> blocks = (List<DbBlock>) map.get("blocks");
            if (map.get("value") == null) continue;
            byte[] bytes = (byte[]) map.get("value");
            for (DbBlock block : blocks) {
                DbBlockDto blockDto = new DbBlockDto();
                blockDto.setName(block.getName());
                blockDto.setAddrParams(block.getAddrParams());
                //补0，为了计算最后一个地址的值
                if (bytes.length == 2) {
                    bytes = new byte[]{bytes[0], bytes[1], 0, 0};
                }
                long value = byteTransform.TransInt16(bytes, 0);
                bytes = SoftBasic.BytesArrayRemoveBegin(bytes, block.getLength());
                blockDto.setValue(String.valueOf(value));
                dto.add(blockDto);
                log.debug("noSign16的数据地址是：{}， 值是：{}", block.getAddrParams(), value);
            }


        }

        return dto;
    }

    @SuppressWarnings("all")
    private List<DbBlockDto> transformByte2NoSign8(List<Map<String, Object>> list) {
        List<DbBlockDto> dto = new ArrayList<>();

        for (Map<String, Object> map : list) {
            List<DbBlock> blocks = (List<DbBlock>) map.get("blocks");
            if (map.get("value") == null) continue;
            byte[] bytes = (byte[]) map.get("value");
            for (int index = 0; index < blocks.size(); index++) {
                DbBlockDto blockDto = new DbBlockDto();
                blockDto.setName(blocks.get(index).getName());
                blockDto.setAddrParams(blocks.get(index).getAddrParams());
                //补0，为了计算最后一个地址的值
//                if (bytes.length == 1) {
//                    bytes = new byte[]{bytes[0], 0};
//                }
//                int value = byteTransform.TransInt16(bytes, 0);
//                bytes = SoftBasic.BytesArrayRemoveBegin(bytes, block.getLength());
                byte aByte = bytes[index];
                blockDto.setValue(String.valueOf(aByte));
                dto.add(blockDto);
                log.debug("地址是：{}， 值是：{}, 次数：{}", blocks.get(index).getAddrParams(), aByte);
            }


        }

        return dto;
    }

    /**
     * 将二进制变量类型的数据进行切分，只需要读取第一个地址的数据，即可
     * 比如 DB3,D309.0， 只需要读取DB3.309，就可以将DB3,D309.0 - DB3,D309.7的值读出来。
     * 因为它是按bit存储的数据，读取时是读的byte值，表示8个bit的数据
     *
     * @param binaryBlocks
     * @return
     */
    private List<DbBlock> filterBinaryBlocks(List<DbBlock> binaryBlocks) {
        ArrayList<DbBlock> result = new ArrayList<>();
        String tempAddr = null;
        for (DbBlock block : binaryBlocks) {
            if (block.getAddrParams().contains(OpcConstant.PERIOD)) {
                String temp = block.getAddrParams().split(OpcConstant.PERIOD_REG)[0];
                if (StringUtils.isNotBlank(tempAddr) && temp.equals(tempAddr)) {
                    continue;
                }
                tempAddr = temp;
            }
            result.add(block);
        }
        return result;
    }

    /**
     * 构建批量读取的异步调用（为多线程构建PLC数据读取任务，一个任务读取19个变量）
     *
     * @param blocks 数据块
     * @return
     */
    private List<CompletableFuture> readPlcFuture(List<DbBlock> blocks) {
        List<CompletableFuture> futures = new ArrayList<>();
        if (blocks.size() == 0) return futures;
        // 19 ，是因为批量读取每次最多只能读19个地址的数据
        // 一个future代表一个task？ futures代表任务集合
        List<List<DbBlock>> lists = Lists.partition(blocks, 19);
        for (List<DbBlock> blockList : lists) {
            CompletableFuture<Map<String, Object>> future =
                    CompletableFuture.supplyAsync(() -> batchRead(blockList), executor);
            futures.add(future);
        }
        return futures;
    }

    /**
     * 获取异步读取数据的结果
     *
     * @param futures 异步调用
     * @return 读取结果
     */
    private List<Map<String, Object>> getPlcDataFromFuture(List<CompletableFuture> futures) {
        List<Map<String, Object>> result = new ArrayList<>(32);
        for (CompletableFuture future : futures) {
            try {
                @SuppressWarnings("all")
                Map<String, Object> map = (Map<String, Object>) future.get();
                result.add(map);
            } catch (InterruptedException | ExecutionException e) {
                throw new OpcException(e);
            }
        }
        return result;
    }

    /**
     * 批量读取PLC数据
     *
     * @param blocks 数据块
     * @return 读取的结果
     */
    private Map<String, Object> batchRead(List<DbBlock> blocks) {
        Map<String, Object> map = new ConcurrentHashMap<>();
        //存放读取的数据块
        map.put("blocks", blocks);
        String[] address = transformDbAddress(blocks);
        short[] length = transformAddressLength(blocks);
        byte[] bytes = PLCUtils.batchReadPLC(address, length);
        //存放读取的数据块的值
        if (bytes != null)
            map.put("value", bytes);
        log.debug("地址是：{}， 读取的byte值是：{}", address, bytes);
        return map;
    }

    /**
     * 将PLC数据块的地址转换成地址数组
     * DB23,DBW2 -> DB23.2
     * I0.0的地址不变
     *
     * @param blocks 数据块
     * @return 地址数组
     */
    private String[] transformDbAddress(List<DbBlock> blocks) {
        List<String> address = blocks.parallelStream().map(block -> {
            if (block.getCGroup().equals("DIO") || block.getAddrParams().startsWith("I")) {
                return block.getAddrParams();
            }
            String addrParams = block.getAddrParams();
            StringBuilder builder = new StringBuilder();
            String offset;
            if (addrParams.contains(",")) {
                String[] split = addrParams.split(",");
                builder.append(split[0]);
                if (split[1].contains(OpcConstant.PERIOD)) {
                    offset = split[1].split(OpcConstant.PERIOD_REG)[0];
                } else {
                    offset = split[1];
                }
            } else {
                offset = addrParams;
            }
            offset = offset.replaceAll("[a-zA-Z]", "");

//            offset = offset.replaceAll("\\.", "");

            return builder.append(OpcConstant.PERIOD).append(offset).toString();
        }).collect(Collectors.toList());

        return address.toArray(new String[address.size()]);
    }

    private short[] transformAddressLength(List<DbBlock> blocks) {
//        List<short> lengths = blocks.parallelStream().map(block -> (int)block.getLength()).collect(Collectors.toList());

        short[] length = new short[blocks.size()];
        for (int index = 0; index < blocks.size(); index++) {
            length[index] = blocks.get(index).getLength().shortValue();
        }

        return length;
    }
}
